﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NLite.Reflection;
using System.Reflection;
using System.ComponentModel;
using System.Collections;

namespace NLite.Mapping.Internal
{
    static class ObjectCreator
    {
        static readonly MethodInfo InternalCreate_Method = typeof(ObjectCreator).GetMethod("InternalCreate", BindingFlags.NonPublic | BindingFlags.Static);

        public static object Create(Type type)
        {
            if (type.IsNullable()) return null;
            var typeCode = Type.GetTypeCode(type);
            switch (typeCode)
            {
                case TypeCode.Boolean: return false;
                case TypeCode.Byte: return default(byte);
                case TypeCode.Char: return default(char);
                case TypeCode.DateTime: return default(DateTime);
                case TypeCode.Decimal: return default(Decimal);
                case TypeCode.Double: return default(double);
                case TypeCode.Int16: return default(Int16);
                case TypeCode.Int32: return default(Int32);
                case TypeCode.Int64: return default(Int64);
                case TypeCode.SByte: return default(SByte);
                case TypeCode.Single: return default(Single);
                case TypeCode.String: return default(string);
                case TypeCode.UInt16: return default(UInt16);
                case TypeCode.UInt32: return default(UInt32);
                case TypeCode.UInt64: return default(UInt64);

            }

            var ctor = type.GetConstructor(Type.EmptyTypes);
            object[] args = null;

            if (ctor != null)
                return ctor.FastInvoke(args);

            return InternalCreate_Method.MakeGenericMethod(type).Invoke(null,null);
        }

        static T InternalCreate<T>() where T:new()
        {
            return new T();
        }

        public static IList CreateList(Type listType, Type listElementType, int length)
        {
            Type type = listType.IsInterface || listType.IsAbstract ? typeof(List<>).MakeGenericType(listElementType) : listType;


            return Activator.CreateInstance(type) as IList;
        }
    }

    class ClassMapperFactory : IMapperFactory
    {
        public bool IsMatch(Type fromType, Type toType)
        {
            return true;
        }

        public IMapper Create(Type fromType, Type toType)
        {
            return new ClassMapper(fromType, toType);
        }
    }

    class ClassToDictionaryMapperFactory : IMapperFactory
    {
        public bool IsMatch(Type fromType, Type toType)
        {
            return !Types.IDictionaryOfStringAndObject.IsAssignableFrom(fromType)
                && Types.IDictionaryOfStringAndObject.IsAssignableFrom(toType)
                ;
        }

        public IMapper Create(Type fromType, Type toType)
        {
            return new ClassToDictionaryMapper(fromType, toType);
        }
    }

    class DictionaryToClassMapperFactory : IMapperFactory
    {
        public bool IsMatch(Type fromType, Type toType)
        {
            return (Types.IDictionaryOfStringAndObject.IsAssignableFrom(fromType)
                    || Types.IDictionaryOfStringAndString.IsAssignableFrom(fromType)
                    || Types.StringDictionary.IsAssignableFrom(fromType)
                    || Types.NameValueCollection.IsAssignableFrom(fromType))
                && (!toType.IsDictionaryType()
                       || !Types.StringDictionary.IsAssignableFrom(toType)
                    || !Types.NameValueCollection.IsAssignableFrom(toType))
                ;
        }

        public IMapper Create(Type fromType, Type toType)
        {
            return new DictonaryToClassMapper(fromType, toType);
        }
    }

    class CollectionMapperFactory : IMapperFactory
    {
        public bool IsMatch(Type fromType, Type toType)
        {
            return (Types.IEnumerable.IsAssignableFrom(fromType)
                    || Types.IEnumerable.IsAssignableFrom(toType))
                    && !Types.IDictionaryOfStringAndObject.IsAssignableFrom(fromType)
                    && !Types.IDictionaryOfStringAndObject.IsAssignableFrom(toType)
                ;
        }

        public IMapper Create(Type fromType, Type toType)
        {
            return new CollectionMapper(fromType, toType);
        }
    }

    class ListSourceMapperFactory : IMapperFactory
    {
        public bool IsMatch(Type fromType, Type toType)
        {
            return (Types.IListSource.IsAssignableFrom(fromType)
                    && (
                        Types.IEnumerable.IsAssignableFrom(toType)
                        && !Types.IDictionaryOfStringAndObject.IsAssignableFrom(toType)
                        || Types.IListSource.IsAssignableFrom(toType)))
                   ||
                   (Types.IListSource.IsAssignableFrom(toType)
                    && (
                        Types.IEnumerable.IsAssignableFrom(fromType)
                        && !Types.IDictionaryOfStringAndObject.IsAssignableFrom(fromType)
                        || Types.IListSource.IsAssignableFrom(fromType)))

                ;
        }

        public IMapper Create(Type fromType, Type toType)
        {
            return new ListSourceMapper(fromType, toType);
        }
    }

    class DataReaderMapperFactory : IMapperFactory
    {
        public bool IsMatch(Type fromType, Type toType)
        {
            return Types.IDataReader.IsAssignableFrom(fromType);
        }

        public IMapper Create(Type fromType, Type toType)
        {
            return new DataReaderMapper(toType);
        }
    }

    class DataRowMapperFactory : IMapperFactory
    {
        public bool IsMatch(Type fromType, Type toType)
        {
            return (Types.DataRow.IsAssignableFrom(fromType) 
                    && !Types.IEnumerable.IsAssignableFrom(toType)
                    && !Types.IDictionaryOfStringAndObject.IsAssignableFrom(toType))
                    ||
                    (Types.DataRow.IsAssignableFrom(toType)
                    && !Types.IEnumerable.IsAssignableFrom(fromType)
                    && !Types.IDictionaryOfStringAndObject.IsAssignableFrom(fromType));
        }

        public IMapper Create(Type fromType, Type toType)
        {
            return new DataRowMapper(toType);
        }
    }

    class DataTableMapperFactory : IMapperFactory
    {
        public bool IsMatch(Type fromType, Type toType)
        {
            return (Types.DataTable.IsAssignableFrom(fromType)
                    && Types.IEnumerable.IsAssignableFrom(toType)
                    && !Types.IDictionaryOfStringAndObject.IsAssignableFrom(toType))
                    ||
                    (Types.DataTable.IsAssignableFrom(toType)
                    && Types.IEnumerable.IsAssignableFrom(fromType)
                    && !Types.IDictionaryOfStringAndObject.IsAssignableFrom(fromType));
        }

        public IMapper Create(Type fromType, Type toType)
        {
            return new DataTableMapper(fromType, toType);
        }
    }

    class EnumMapperFactory : IMapperFactory
    {
        public bool IsMatch(Type fromType, Type toType)
        {
            return
                (TypeHelper.IsEnumType(fromType) && TypeHelper.CanConvertToEnum(toType))
                || (TypeHelper.IsEnumType(toType) && TypeHelper.CanConvertToEnum(fromType));
        }

        public IMapper Create(Type fromType, Type toType)
        {
            return new EnumMapper(fromType, toType);
        }
    }

    class NullableMapperFactory : IMapperFactory
    {
        public bool IsMatch(Type fromType, Type toType)
        {
            return fromType.IsNullable()
                || toType.IsNullable();
        }

        public IMapper Create(Type fromType, Type toType)
        {
            return new NullableMapper(fromType, toType);
        }
    }

    class PrimitiveMapperFactory : IMapperFactory
    {
        public bool IsMatch(Type fromType, Type toType)
        {
            var fromTypeCode = Type.GetTypeCode(fromType);
            var toTypeCode = Type.GetTypeCode(toType);

            if (fromTypeCode == TypeCode.Object
                || toTypeCode == TypeCode.Object
                || fromTypeCode == TypeCode.DBNull
                || toTypeCode == TypeCode.DBNull)
                return false;
            return true;
        }

        public IMapper Create(Type fromType, Type toType)
        {
            return new PrimitiveMapper(fromType, toType);
        }
    }

    class TypeConverterMapperFactory : IMapperFactory
    {
        public bool IsMatch(Type fromType, Type toType)
        {
            return TypeDescriptor.GetConverter(fromType).CanConvertTo(toType);
        }

        public IMapper Create(Type fromType, Type toType)
        {
            return new TypeConverterMapper(fromType, toType);
        }
    }

    class StringMapperFactory : IMapperFactory
    {
        public bool IsMatch(Type fromType, Type toType)
        {
            return toType == Types.String;
        }

        public IMapper Create(Type fromType, Type toType)
        {
            return new StringMapper(fromType, toType);
        }
    }

    class FlagsEnumMapperFactory : IMapperFactory
    {
        public bool IsMatch(Type fromType, Type toType)
        {
            return
                TypeHelper.IsFlagsEnum(toType)
                && TypeHelper.CanConvertToEnum(fromType)
                ;
        }



        public IMapper Create(Type fromType, Type toType)
        {
            return new FlagsEnumMapper(fromType, toType);
        }
    }

    class DictionaryMapperFactory : IMapperFactory
    {
        public bool IsMatch(Type fromType, Type toType)
        {
            return fromType.IsDictionaryType() && toType.IsDictionaryType();
        }

        public IMapper Create(Type fromType, Type toType)
        {
            return new DictionaryMapper(fromType, toType);
        }
    }
}
